// This file is part of Notepad4.
// See License.txt for details about distribution and modification.
//! Lexer for Mathematica

#include <cassert>
#include <cstring>

#include <string>
#include <string_view>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

using namespace Lexilla;

namespace {

//KeywordIndex++Autogenerated -- start of section automatically generated
enum {
	KeywordIndex_Keyword = 0,
	MaxKeywordSize = 16,
};
//KeywordIndex--Autogenerated -- end of section automatically generated

enum class SlotType {
	None,
	Sharp,		// #name
	Backtick,	// `name`
};

void ColouriseMathematicaDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) {
	const bool fold = styler.GetPropertyBool("fold");

	SlotType slotType = SlotType::None;
	int outerState = SCE_MATHEMATICA_DEFAULT;
	int commentLevel = 0;
	int levelCurrent = SC_FOLDLEVELBASE;

	StyleContext sc(startPos, lengthDoc, initStyle, styler);
	if (sc.currentLine > 0) {
		commentLevel = styler.GetLineState(sc.currentLine - 1);
		levelCurrent = styler.LevelAt(sc.currentLine - 1) >> 16;
	}

	int levelNext = levelCurrent;
	if (startPos == 0 && sc.Match('#', '!')) {
		// WolframScript
		sc.SetState(SCE_MATHEMATICA_COMMENTLINE);
		sc.Forward();
	}

	while (sc.More()) {
		switch (sc.state) {
		case SCE_MATHEMATICA_OPERATOR:
			sc.SetState(SCE_MATHEMATICA_DEFAULT);
			break;

		case SCE_MATHEMATICA_NUMBER:
			if (!IsDecimalNumber(sc.chPrev, sc.ch, sc.chNext)) {
				sc.SetState(SCE_MATHEMATICA_DEFAULT);
			}
			break;

		case SCE_MATHEMATICA_IDENTIFIER:
		case SCE_MATHEMATICA_SLOT:
			if (!IsIdentifierChar(sc.ch)) {
				if (sc.state == SCE_MATHEMATICA_IDENTIFIER) {
					char s[MaxKeywordSize];
					sc.GetCurrent(s, sizeof(s));
					if (keywordLists[KeywordIndex_Keyword].InList(s)) {
						sc.ChangeState(SCE_MATHEMATICA_KEYWORD);
					}
					sc.SetState(SCE_MATHEMATICA_DEFAULT);
				} else {
					if (slotType == SlotType::Backtick) {
						if (sc.ch != '`') {
							sc.ChangeState(SCE_MATHEMATICA_STRING);
						} else {
							sc.Forward();
						}
					}
					sc.SetState(outerState);
					continue;
				}
			}
			break;

		case SCE_MATHEMATICA_COMMENT:
			if (sc.Match('(', '*')) {
				commentLevel++;
				sc.Forward();
			} else if (sc.Match('*', ')')) {
				sc.Forward();
				commentLevel--;
				if (commentLevel == 0) {
					levelNext--;
					sc.ForwardSetState(SCE_MATHEMATICA_DEFAULT);
				}
			}
			break;

		case SCE_MATHEMATICA_COMMENTLINE:
			if (sc.atLineStart) {
				sc.SetState(SCE_MATHEMATICA_DEFAULT);
			}
			break;

		case SCE_MATHEMATICA_STRING:
			if (sc.ch == '\\') {
				sc.Forward();
			} else if (sc.ch == '#' || sc.ch == '`') {
				// TODO: highlight <* expr *>
				outerState = SCE_MATHEMATICA_STRING;
				slotType = (sc.ch == '#') ? SlotType::Sharp : SlotType::Backtick;
				sc.SetState(SCE_MATHEMATICA_SLOT);
			} else if (sc.ch == '\"') {
				sc.ForwardSetState(SCE_MATHEMATICA_DEFAULT);
			}
			break;
		}

		if (sc.state == SCE_MATHEMATICA_DEFAULT) {
			if (sc.Match('(', '*')) {
				commentLevel = 1;
				levelNext++;
				sc.SetState(SCE_MATHEMATICA_COMMENT);
				sc.Forward();
			} else if (sc.ch == '\"') {
				sc.SetState(SCE_MATHEMATICA_STRING);
			} else if (IsNumberStart(sc.ch, sc.chNext)) {
				sc.SetState(SCE_MATHEMATICA_NUMBER);
			} else if (sc.ch == '#') {
				outerState = SCE_MATHEMATICA_DEFAULT;
				slotType = SlotType::Sharp;
				sc.SetState(SCE_MATHEMATICA_SLOT);
			} else if (IsIdentifierStart(sc.ch)) {
				sc.SetState(SCE_MATHEMATICA_IDENTIFIER);
			} else if (IsAGraphic(sc.ch)) {
				sc.SetState(SCE_MATHEMATICA_OPERATOR);
				if (sc.ch == '{' || sc.ch == '[' || sc.ch == '(') {
					levelNext++;
				} else if (sc.ch == '}' || sc.ch == ']' || sc.ch == ')') {
					levelNext--;
				}
			}
		}
		if (sc.atLineEnd) {
			if (fold) {
				levelNext = sci::max(levelNext, SC_FOLDLEVELBASE);
				const int levelUse = levelCurrent;
				int lev = levelUse | (levelNext << 16);
				if (levelUse < levelNext) {
					lev |= SC_FOLDLEVELHEADERFLAG;
				}
				styler.SetLevel(sc.currentLine, lev);
			}

			styler.SetLineState(sc.currentLine, commentLevel);
			levelCurrent = levelNext;
		}
		sc.Forward();
	}

	sc.Complete();
}

}

extern const LexerModule lmMathematica(SCLEX_MATHEMATICA, ColouriseMathematicaDoc, "mathematica");
